Skip to content

perf(db): eliminate closure allocation in getMemTables cleanup#2291

Open
shaunpatterson wants to merge 1 commit into
dgraph-io:mainfrom
shaunpatterson:perf/db-getmemtables-no-closure
Open

perf(db): eliminate closure allocation in getMemTables cleanup#2291
shaunpatterson wants to merge 1 commit into
dgraph-io:mainfrom
shaunpatterson:perf/db-getmemtables-no-closure

Conversation

@shaunpatterson
Copy link
Copy Markdown

Summary

getMemTables previously returned (tables, cleanupFunc) where cleanupFunc was a closure capturing the tables slice. That closure escaped to the heap on every call — one extra allocation per iterator construction in the rollup path.

Split into:

func (db *DB) getMemTables() []*memTable
func decrMemTables([]*memTable)

Callers do defer decrMemTables(tables) after the getMemTables call. The slice is fully populated before the defer evaluates its argument, so the captured slice header points to the correct backing array; no behavioral change.

decrMemTables is a free function (no receiver) so the deferred call record doesn't pay for a *DB pointer alongside the slice header.

Benchmark — BenchmarkRollupKeyIterator

B/op allocs/op
before 801 14
after 769 13

Test plan

  • go test ./... passes
  • All three callers (db.get, txn.NewIterator, stream_writer) verified to release refs via defer

🤖 Generated with Claude Code

getMemTables previously returned (tables, cleanupFunc) where cleanupFunc
was a closure capturing the tables slice. That closure escaped to the
heap on every call (one extra allocation per iterator construction in
the rollup path).

Split into:
  func (db *DB) getMemTables() []*memTable
  func decrMemTables([]*memTable)

Callers do `defer decrMemTables(tables)` after the getMemTables call.
The slice is fully populated before the defer evaluates its argument,
so the captured slice header points to the correct backing array; no
behavioral change.

decrMemTables is a free function (no receiver) so the deferred call
record doesn't pay for a *DB pointer alongside the slice header.

Saves 1 alloc and 32 B per iterator construction on
BenchmarkRollupKeyIterator:

  before:  801 B/op, 14 allocs/op
  after:   769 B/op, 13 allocs/op

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@shaunpatterson shaunpatterson requested a review from a team as a code owner May 26, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant